package com.itcoon.cloud.framework.transform.utils;

import com.itcoon.cloud.framework.constants.CommonCoreConstants;
import com.itcoon.cloud.framework.model.KeyValue;
import com.itcoon.cloud.framework.transform.annotation.AssembleModelProperty;
import com.itcoon.cloud.framework.transform.exception.AssembleException;
import com.itcoon.cloud.framework.transform.proxy.IProxy;
import com.itcoon.cloud.framework.utils.FieldUtil;
import com.itcoon.cloud.framework.utils.MethodUtil;
import com.itcoon.cloud.framework.utils.SpringReflectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

public class TransformServiceUtils {

    public static Object getCascadeObjFromValue(Object pojo, String value) throws Exception {
        if (!value.contains(".")) return pojo;
        Class<?> belongClass = pojo.getClass();
        String[] datas = value.split("\\.");
        Object cascadeObj = pojo;
        for (String data : datas) {
            String cascadeMethodName = CommonCoreConstants.GETTER_PREFIX + data.substring(0, 1).toUpperCase() + data.substring(1);
            Method cascadeMethod = belongClass.getMethod(cascadeMethodName);
            belongClass = cascadeMethod.getReturnType();
            cascadeObj = cascadeMethod.invoke(cascadeObj);
        }
        return cascadeObj;
    }

    public static Object getBelongFromValue(Object pojo, String value){
        Assert.notNull(pojo, "pojo must be not null");
        if (value.equals("") || !value.contains(".")) return pojo;
        Object cascadeObj = pojo;
        String[] datas = value.split("\\.");
        for (int i = 0, n = datas.length - 1; i < n; i++) {
            cascadeObj = cascadeObj == null ? null : SpringReflectionUtils.getMethodValue(cascadeObj, MethodUtil.formatGetterName(datas[i]), null, null);
        }
        return cascadeObj;
    }



    public static Method findGetterFromValue(Class<?> originClass, String value) {
        Assert.notNull(originClass, "origin class must be not null");
        Assert.hasText(value, "value must has text.");
        if(!value.contains(".")){
            return ReflectionUtils.findMethod(originClass, MethodUtil.formatGetterName(value));
        }
        String[] datas = value.split("\\.");
        Class<?> cascadeClass = originClass;
        for (int i = 0, n = datas.length; i < n; i++) {
            String data = datas[i];
            Method cascadeGetter = ReflectionUtils.findMethod(cascadeClass, CommonCoreConstants.GETTER_PREFIX + data.substring(0,1).toUpperCase() + data.substring(1));
            if(i == n - 1){
                return cascadeGetter;
            }
            if(cascadeGetter == null){
                break;
            }
            cascadeClass = cascadeGetter.getReturnType();
        }
        throw new AssembleException("Illegal originAttribute#" + value);
    }

    /**
     * 是否是代理对象
     * @param clazz
     * @return
     */
    public static boolean isAssembleProxy(Class<?> clazz){
        return clazz.getName().contains("$") && IProxy.class.isAssignableFrom(clazz);
    }

    /**
     * 获取代理对象的原来类型
     * @return
     */
    public static <T,R> Class<?> getProxyOrigin(IProxy<T,R> proxy) {
        Class<? extends Proxy> clazz = (Class<? extends Proxy>) proxy.getClass();
        if(clazz.getName().contains("$$")){
            return clazz.getSuperclass();
        }
        if(Proxy.isProxyClass(clazz)){
            IProxy<T,R> sourceProxy = (IProxy<T,R>)SpringReflectionUtils.getField(proxy, "h");
            return  (Class<?>) SpringReflectionUtils.getField(sourceProxy, "targetClass");
        }
        throw new IllegalArgumentException("UNKNOWN PROXY FOUND");
    }

    /**
     * 获取对象的原型。
     * 1. 代理对象
     * 2. 匿名对象
     *
     */
    public static <T> Class<T> getOriginClass(T source){
        Class<?> sourceType = source.getClass();
        if(isAssembleProxy(sourceType)){
            return getProxyOrigin((IProxy) source);
        }
        if(sourceType.isAnonymousClass()){
            Class<?> superClass = sourceType.getSuperclass();
            if(Object.class.equals(superClass)){
                Type[] genericInterfaces = sourceType.getGenericInterfaces();
                Optional<Type> originClazzOp = Arrays.stream(genericInterfaces).filter(interClazz->((Class)interClazz).isAssignableFrom(sourceType)).findAny();
                return (Class<T>)originClazzOp.orElse(null);
            }
            return (Class<T>)superClass;
        }
        return (Class<T>)sourceType;
    }


    public static <T> List<Descriptor> getDescriptors(Class<T> clazz){
        List<Descriptor> descriptors = new ArrayList<>();
        if(clazz.isInterface()){
            Class<?>[] interfaces = clazz.getInterfaces();
            Class<?>[] classes = Arrays.copyOf(interfaces, interfaces.length + 1);
            classes[interfaces.length] = clazz;
            for (Class<?> c : classes) {
                for (Method method : c.getMethods()) {
                    if(MethodUtil.isGetter(method)){
                        AssembleModelProperty assembleModelProperty = method.getAnnotation(AssembleModelProperty.class);
                        Descriptor descriptor = new Descriptor(null, method, null, assembleModelProperty);
                        descriptors.add(descriptor);
                    }
                }
            }
        }else{
            List<Field> fields = FieldUtil.getAllDeclareField(clazz).stream().filter(field-> !Modifier.isStatic(field.getModifiers())).collect(Collectors.toList());
            List<Method> methods = Arrays.stream(clazz.getMethods()).filter(MethodUtil::isPropertyMethod).collect(Collectors.toList());
            Set<String> methodAttributes = methods.stream().map(MethodUtil::getMethodAttribute).collect(Collectors.toSet());
            Map<String, Method> methodMap = methods.stream().collect(Collectors.toMap(Method::getName, method -> method));
            Map<String, Field> fieldMap = fields.stream().collect(Collectors.toMap(Field::getName, field->field));
            Set<String> attributes = new HashSet<>(fieldMap.keySet());
            attributes.addAll(methodAttributes);
            for (String attribute : attributes) {
                Method getter = methodMap.get(MethodUtil.formatGetterName(attribute));
                Method setter = methodMap.get(MethodUtil.formatSetterName(attribute));
                Field field = fieldMap.get(attribute);
                AssembleModelProperty assembleModelProperty = getter == null ? null : getter.getAnnotation(AssembleModelProperty.class);
                if(field != null){
                    getter = methodMap.get(FieldUtil.getGetterName(field));
                    setter = methodMap.get(FieldUtil.getSetterName(field));
                    assembleModelProperty = assembleModelProperty == null ? field.getAnnotation(AssembleModelProperty.class) : assembleModelProperty;
                }
                Descriptor descriptor = new Descriptor(field, getter, setter, assembleModelProperty);
                descriptors.add(descriptor);
            }
        }
        return descriptors;
    }


    public static KeyValue<Class<?>, Type[]> explain(Type propertyType, Class<?> targetClass, Type[] actualTypes){
        KeyValue<Class<?>, Type[]> keyValue = new KeyValue<>();
        if(propertyType instanceof ParameterizedType){
            ParameterizedType parameterizedType = (ParameterizedType) propertyType;
            keyValue.setKey((Class)parameterizedType.getRawType());
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            Type[] propertyActualTypes = new Type[actualTypeArguments.length];
            for (int i = 0; i < actualTypeArguments.length; i++) {
                if(actualTypeArguments[i] instanceof TypeVariable){
                    // 如果是 private List<T> fieldName;
                    TypeVariable typeVariable = (TypeVariable) actualTypeArguments[i];
                    if(typeVariable.getGenericDeclaration().equals(targetClass)){
                        TypeVariable<? extends Class<?>>[] typeParameters = targetClass.getTypeParameters();
                        List<String> typeNames = Arrays.stream(typeParameters).map(TypeVariable::getName).collect(Collectors.toList());
                        int index = typeNames.indexOf(typeVariable.getName());
                        if(index != -1 && actualTypes.length > index){
                            propertyActualTypes[i] = actualTypes[i];
                        }
                    }else{
                       // 如果泛型不是target声明的
                        propertyActualTypes[i] = null;
                    }
                }else{
                    // 如果是 private List<?> fieldName; 或者 private List<Object> fieldName;
                    propertyActualTypes[i] = actualTypeArguments[i];
                }
            }
            keyValue.setValue(propertyActualTypes);
        }else if(propertyType instanceof TypeVariable){
            // 如果是private T fieldName;
            TypeVariable typeVariable = (TypeVariable) propertyType;
            if(typeVariable.getGenericDeclaration().equals(targetClass)){
                TypeVariable<? extends Class<?>>[] typeParameters = targetClass.getTypeParameters();
                List<String> typeNames = Arrays.stream(typeParameters).map(TypeVariable::getName).collect(Collectors.toList());
                int index = typeNames.indexOf(typeVariable.getName());
                if(index != -1 && actualTypes.length > index){
                    Type finalType = actualTypes[index];
                    if(finalType instanceof ParameterizedType){
                        ParameterizedType parameterizedType = (ParameterizedType) finalType;
                        keyValue.setKey((Class)parameterizedType.getRawType());
                        keyValue.setValue(parameterizedType.getActualTypeArguments());
                    }else if(finalType instanceof Class){
                        keyValue.setKey((Class<?>) finalType);
                        keyValue.setValue(new Type[0]);
                    }
                }
            }else{
                // Todo: 对于泛型由方法决定的方式
                keyValue.setKey(null);
                keyValue.setValue(new Type[0]);
            }
        }else if(propertyType instanceof Class){
            keyValue.setKey((Class)propertyType);
            keyValue.setValue(new  Type[0]);
        }
        return keyValue;
    }

    public static class Descriptor {

        private Field field;

        private Method getter;

        private Method setter;

        private Annotation annotation;

        public Descriptor(Field field, Method getter, Method setter, Annotation annotation) {
            this.field = field;
            this.getter = getter;
            this.setter = setter;
            this.annotation = annotation;
        }

        public Field getField() {
            return field;
        }

        public void setField(Field field) {
            this.field = field;
        }

        public Method getGetter() {
            return getter;
        }

        public void setGetter(Method getter) {
            this.getter = getter;
        }

        public Method getSetter() {
            return setter;
        }

        public void setSetter(Method setter) {
            this.setter = setter;
        }

        public Annotation getAnnotation() {
            return annotation;
        }

        public void setAnnotation(Annotation annotation) {
            this.annotation = annotation;
        }

        public String getAttribute(){
            return this.field != null ?  field.getName() : this.getter != null ? MethodUtil.getMethodAttribute(this.getter) : this.setter != null ? MethodUtil.getMethodAttribute(this.setter) : null;
        }

        public String getOriginAttribute() {
            if(getAnnotation() != null){
                AssembleModelProperty assembleModelProperty = (AssembleModelProperty) getAnnotation();
                return assembleModelProperty.mapValue();
            }
            return getAttribute();
        }

        public Class<?> getTye(){
            if(this.field != null){
                return this.field.getType();
            }else if(this.getter != null){
                return this.getter.getReturnType();
            }else if(this.setter != null){
                return this.setter.getParameters()[0].getType();
            }
            return null;
        }
    }
}
